#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#
# Conexión 3G
# Copyright (C) 2009 Plan Ceibal
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Contact information: comunidad@plan.ceibal.edu.uy
# Plan Ceibal http://www.ceibal.edu.uy

import pango
import gtk
import os, sys, subprocess
import logging
import fcntl, time, select
import gobject
import SetConfiguracion, InterfazGlade_Setup
import gtk.glade
import InterfazGlade
import string, datetime

PATH_WVDIAL = '/usr/bin/wvdial-wrapper'
# Constantes de Configuración
AskPasswordCons = False
BaudCons = '460800'
StupidModeCons = True

class InterfazGlade:
	def __init__(self, runaslib=True):
		# Cargar Glade Xml
		self.xml = gtk.glade.XML("Conexion3G.glade")
		# Obtener controles
		# Ventana Configuración
		self.setupwindow = self.xml.get_widget('setupwindow').get_child()
		self.setupwindow.parent.remove(self.setupwindow)
		# Ventana Conexión
		self.ventanaprincipal = self.xml.get_widget('connectionwindow')
		self.connectionwindow = self.ventanaprincipal.get_child()
		self.connectionwindow.parent.remove(self.connectionwindow)
		# Botones		
		self.btnsetup = self.xml.get_widget('btnsetup')
		self.btnconnect = self.xml.get_widget('connect')
		self.btndisconnect = self.xml.get_widget('disconnect')
		self.btnsetupclose = self.xml.get_widget('btnsetupclose')
		# Cuadros Texto
		self.userNameTxt = self.xml.get_widget('username')
		self.passwordTxt = self.xml.get_widget('password')
		self.phoneNumber = self.xml.get_widget('phoneNum')
		self.PinTxt =  self.xml.get_widget('Pin')
		self.PukTxt =  self.xml.get_widget('Puk')
		# Otros	
		self.progressbar = self.xml.get_widget('progressbar')
		self._progressBarHandler = None
		self.labelestado = self.xml.get_widget('labelestado')
		self.labeltiempoconexion = self.xml.get_widget('labeltiempoconexion')
		self.labeldatosenviados = self.xml.get_widget('labeldatosenviados')
		self.labeldatosrecibidos = self.xml.get_widget('labeldatosrecibidos')
		# Conectar
		self.xml.signal_autoconnect(self)
		# Configuración 3G 
		try:
			self.config3G = SetConfiguracion.getModem3GConfiguracion()
			# Logica de Setup
			self.interfazGladeSetup = InterfazGlade_Setup.InterfazGladeSetup(self)
			# Se cargan los controles con la configuración Anterior
			self.userNameTxt.set_text(self.config3G.Username)  
			self.passwordTxt.set_text(self.config3G.Password)  
			self.phoneNumber.set_text(self.config3G.Phone)  
			self.PinTxt.set_text(self.config3G.Pin)
			self.PukTxt.set_text(self.config3G.Puk)
		except ExcepcionErrorInstalacion, e:
			self.printMensaje(str(e)) 
		# Actualizar estado segun haya un proceso previo corriendo
		self.actualizarEstado()		
		self.actualizarDisplayEstado()
		self.timerCount()	

		self._logger = logging.getLogger('Conexion3G-activity')
		self._logger.setLevel(logging.DEBUG)
###################### Handlers ##############################


	def conectar(self, widget):
		# Start Proceso
		try:
			# Chequeo si ya exista una conexión
			self.actualizarEstado()		
			self.actualizarDisplayEstado()
			self.timerCount()
			if (self.estadoconexion=="Desconectada"):
				self.InicializacionPin = False	
				self.requierePuk = False	
				self.guardarConfiguracion()	
				self.bajarServicioNetworkManager()

				command=[ PATH_WVDIAL, 'connect', 'Connection', '--config', '/home/local/.wvdial.conf']
				proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
				task = self.checkProcesoConexionDesconexion(proc, "Conexion")
				gobject.idle_add(task.next)
		except ExcepcionConfiguracionInvalida, e:
			self.printMensaje(str(e))
		except Exception, e:
			self.printMensaje("Error ejecutando wvdial-wrapper. Fallo en la ejecución:" + str(e))
		# Fin Proceso

	def desconectar(self, widget=None):
		# Start Proceso
		try:
			command=[ PATH_WVDIAL, 'disconnect']
			proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
			task = self.checkProcesoConexionDesconexion(proc, "Desconexion")
			gobject.idle_add(task.next)

		except OSError, e:
			self.printMensaje("Error ejecutando wvdial-wrapper. Fallo en la ejecución:" + str(e))
		# Fin Proceso

	def on_close(self, *args):
		gtk.main_quit()

	# Timer: tiempo conectado
	def timerCount(self):
		if (self.estadoconexion=="Conectada"):
			if (self.timecount % 5 == 0):
				try:
					datosConexion = self.getDatosEnviadosConexion3G()
				except  ExcepcionInterfazPPP0Desactivada, e:
					# Chequeo si ya exista una conexión
					self.estadoconexion = "Desconectada"
					self.actualizarDisplayEstado()
					self.levantarServicioNetworkManager()
					return False
				self.labeldatosenviados.set_text('%.2f' % datosConexion.BytesEnviados)
				self.labeldatosrecibidos.set_text('%.2f' % datosConexion.BytesRecibidos)
			self.timecount = self.timecount + 1
			time = datetime.datetime.fromtimestamp(self.timecount)
	    		self.labeltiempoconexion.set_text(time.strftime('%H : %M : %S'))
			return True
		else:
			return False

	# Timer Barra de Progreso
	def timerProgressBar(self):
		self.progressbar.pulse()
		return True	

###################### Auxiliares ##############################

	def actualizarEstado(self):
		self.estadoconexion = self.getStatusConexion()

	# Actualiza la interfaz segun el estado de la conexión
	def actualizarDisplayEstado(self):
		self.labelestado.set_text(self.estadoconexion)
		if (self.estadoconexion=="Desconectada"):
			self.btnconnect.set_sensitive(True)
			self.btndisconnect.set_sensitive(False)
	    		self.labeltiempoconexion.set_text("00 : 00 : 00")
			self.labeldatosenviados.set_text('%.2f' % 0.00)
			self.labeldatosrecibidos.set_text('%.2f' % 0.00)
			self.timecount = 0
		if (self.estadoconexion=="Conectada"):
			self.btnconnect.set_sensitive(False)
			self.btndisconnect.set_sensitive(True)
			gobject.timeout_add (1000, self.timerCount) 

	# Interactua con el proceso de conexión y desconexión
	def checkProcesoConexionDesconexion(self, proc, tipoProceso):
		returncode = proc.poll()
		returnstring = ""
		conexion = False
		obtenerDns = False
		errorFlag = False
		errorMsg = ""
		self._logger.debug("Chequeando proceso: " + tipoProceso)

		# Archivos para comunicacion con los procesos
		outfile = proc.stdout
		outfd = self.getProcFilesDescriptors(outfile)
		outfileerr = proc.stderr
		outfderr = self.getProcFilesDescriptors(outfileerr)

		if (self._progressBarHandler == None):
			self.progressbar.show()
			self.progressbar.set_fraction(0.0)	
			self._progressBarHandler = gobject.timeout_add(1000, self.timerProgressBar) 

		# Mientras no termine el proceso
		# Mientras no se logre la conexión y la configuración dns sea manual O no se obtengan los dns y la configuración dns sea automatica 
		while returncode == None and ((not conexion and not self.config3G.AutoDns) or (not obtenerDns and self.config3G.AutoDns)):
			# Chequeo Proceso
			returncode = proc.poll()

			ready = select.select([outfd],[],[], 0.2) # wait for input
			if outfd in ready[0]:
				outchunk = outfile.read()
				if outchunk <> "":
					self._logger.debug("Salida Proceso StdOut:\n\n" + outchunk + "\n\n")
					returnstring = outchunk
			
			ready = select.select([outfderr],[],[],0.2) # wait for error
			if outfderr in ready[0]:
				outchunk = outfileerr.read()
				if (outchunk <> ""):
					self._logger.debug("Salida Proceso StdError:\n\n" + outchunk + "\n\n")
					returnstring = outchunk
					
					outchunk = outchunk.replace(' ','')
					if (string.find(outchunk, "CONNECT")<>-1):
						conexion = True
						self.estadoconexion = "Conectada"
					if (string.find(outchunk, "primaryDNSaddress")<>-1 and conexion):
						self.config3G.Dns1 = outchunk.partition("primaryDNSaddress")[2].split()[0]
					if (string.find(outchunk, "secondaryDNSaddress")<>-1 and conexion):
						obtenerDns = True
						self.config3G.Dns2 = outchunk.partition("secondaryDNSaddress")[2].split()[0]
			yield True

		self._logger.debug("Salida del proceso: " + str(returncode) + " - String Salida: " + returnstring)

		# Mensajes al usuario
		if (tipoProceso == "Conexion"):
			if (conexion):
				errorFlag = False
				errorMsg = "Conexión establecida"
				try:
					SetConfiguracion.setModem3gDnsConfiguracion(self.config3G.Dns1, self.config3G.Dns2)
				except Exception, e:
					self.printMensaje(str(e))
			else: 
				if (returnstring.find("No such file or directory")<>-1):
					errorFlag = True
					errorMsg ="No se pudo establecer la comunicación con el módem. Verifique que este conectado y que las propiedades de conexión sean correctas"
				elif (returnstring.find("CME ERROR: SIM PIN required")<>-1):
					if (not self.InicializacionPin):
						self.ejecutarInicializacionPinModem()
						yield False
					else: 
						errorFlag = True
						errorMsg = "El Pin ingresado no es correcto"
				elif (returnstring.find("CME ERROR: SIM PUK required")<>-1):
					if (not self.InicializacionPin):
						self.requierePuk = True
						self.ejecutarInicializacionPinModem()
						yield False
					else: 
						errorFlag = True
						errorMsg = "El Pin/Puk ingresado no es correcto"
				else:
					errorFlag = True
					errorMsg = "No se pudo establecer la conexión. Verifique las propiedades de conexión:\n" + returnstring

		if (tipoProceso == "Desconexion"):
			if (returnstring.find("no process killed")<>-1):
				errorFlag = True
				errorMsg = "No se encontro una conexión 3G activa"
				self.estadoconexion = "Desconectada"
			if (not returnstring.find("no process killed")<>-1):
				errorFlag = True
				errorMsg = "Conexión Finalizada"
				self.estadoconexion = "Desconectada"
		self.actualizarDisplayEstado()
		self.progressbar.hide()
		gobject.source_remove(self._progressBarHandler)
		self._progressBarHandler = None
		# Bajar-Subir NetworkManager
		if (self.estadoconexion=="Desconectada"):
			self.levantarServicioNetworkManager()
		if (errorFlag): 
			self.printMensaje(errorMsg)
		yield False

	def ejecutarInicializacionPinModem(self):
		self.guardarConfiguracion()
		command=[ PATH_WVDIAL, 'connect', 'Pin', 'Connection', '--config', '/home/local/.wvdial.conf']
		proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
		task = self.checkProcesoInicializacionPin(proc)
		gobject.idle_add(task.next)

	def getProcFilesDescriptors(self, outfile):
		outfd=outfile.fileno()
		file_flags = fcntl.fcntl(outfd, fcntl.F_GETFL)
		fcntl.fcntl(outfd, fcntl.F_SETFL, file_flags | os.O_NDELAY)
		return outfd

	# Interactua con el proceso de Inicializacion Modem
	def checkProcesoInicializacionPin(self, proc):
		self._logger.debug("Chequeando proceso: Inicializacion")
		returncode = proc.poll()
		returnstring = ""
		# Archivos para comunicacion con los procesos
		outfile = proc.stdout
		outfd = self.getProcFilesDescriptors(outfile)
		outfileerr = proc.stderr
		outfderr = self.getProcFilesDescriptors(outfileerr)
		
		if (self._progressBarHandler == None):
			self.progressbar.show()
			self.progressbar.set_fraction(0.0)	
			self._progressBarHandler = gobject.timeout_add(1000, self.timerProgressBar) 

		while (returncode == None): 
			# Chequeo Proceso
			returncode = proc.poll()

			ready = select.select([outfd],[],[], 0.2) # wait for input
			if outfd in ready[0]:
				outchunk = outfile.read()
				if outchunk <> "":
					self._logger.debug("Salida Proceso StdOut:\n\n" + outchunk + "\n\n")
					returnstring = outchunk
			
			ready = select.select([outfderr],[],[],0.2) # wait for error
			if outfderr in ready[0]:
				outchunk = outfileerr.read()
				if (outchunk <> ""):
					self._logger.debug("Salida Proceso StdError:\n\n" + outchunk + "\n\n")
					returnstring = outchunk
			yield True

		self._logger.debug("Salida del proceso Inicializacion: " + str(returncode) + " - String Salida: " + returnstring)
		self.InicializacionPin = True
		# Se agenda un nuevo intento de conexión en 5 sec
		gobject.timeout_add (5000, self.timerConnection) 
		yield False

	# Lanza la ejecución de la conexión
	def timerConnection(self):
		command=[ PATH_WVDIAL, 'connect', 'Connection', '--config', '/home/local/.wvdial.conf']
		proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
		task = self.checkProcesoConexionDesconexion(proc, "Conexion")
		gobject.idle_add(task.next)
		return False

	def printMensaje(self, texto):
		md = gtk.MessageDialog(self.ventanaprincipal, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, gtk.BUTTONS_CLOSE, texto)
		md.run()
		md.destroy()
	
	def guardarConfiguracion(self):
		# Usuario
		if (self.userNameTxt.get_text() <> ""):
			self.config3G.Username = self.userNameTxt.get_text()
		else:
			raise ExcepcionConfiguracionInvalida("Debe indicar el usuario del servicio")
		# Password
		if (self.passwordTxt.get_text() <> ""):
			self.config3G.Password = self.passwordTxt.get_text()
		else:
			raise ExcepcionConfiguracionInvalida("Debe indicar una contraseña")
		self.config3G.AskPassword = AskPasswordCons 
		# Teléfono
		if (self.phoneNumber.get_text() <> ""):
			self.config3G.Phone = self.phoneNumber.get_text()
		else:
			raise ExcepcionConfiguracionInvalida("Debe indicar el teléfono a marcar")
		# Pin Puk
		self.config3G.Pin = self.PinTxt.get_text()
		self.config3G.Puk = self.PukTxt.get_text()
		self.config3G.requierePuk = self.requierePuk
		# Módem
		if ( self.interfazGladeSetup.deviceTxt.get_active_text() <> ""):
			self.config3G.Modem = self.interfazGladeSetup.deviceTxt.get_active_text()
		else:
			raise ExcepcionConfiguracionInvalida("Debe indicar el módem conectado al equipo, o pulsar Detectar en la ficha de configuración")
		# Tipo de Módem
		if ( self.interfazGladeSetup.typeTxt.get_active_text() <> ""):
			self.config3G.ModemType = self.interfazGladeSetup.typeTxt.get_active_text()
		else:
			raise ExcepcionConfiguracionInvalida("Debe indicar el tipo de módem conectado al equipo, o pulsar Detectar en la ficha de configuración")

		self.config3G.Baud = BaudCons
		self.config3G.stupid_mode = StupidModeCons 

		# Dns
		self.config3G.ManualDns = self.interfazGladeSetup.manual_dns.get_active()
		self.config3G.AutoDns = self.interfazGladeSetup.auto_dns.get_active()
		if (self.interfazGladeSetup.dns1.get_text() <> "" or self.config3G.AutoDns):
			self.config3G.Dns1 = self.interfazGladeSetup.dns1.get_text()
		else:
			raise ExcepcionConfiguracionInvalida("Debe indicar los Dns de su proveedor de internet")
		if (self.interfazGladeSetup.dns2.get_text() <> "" or self.config3G.AutoDns):
			self.config3G.Dns2 = self.interfazGladeSetup.dns2.get_text()
		else:
			raise ExcepcionConfiguracionInvalida("Debe indicar los Dns de su proveedor de internet")
		# Palabras de Configuración			
		self.config3G.InitString1 = self.interfazGladeSetup.InitString1.get_text()
		self.config3G.InitString2 = self.interfazGladeSetup.InitString2.get_text()
		self.config3G.InitString3 = self.interfazGladeSetup.InitString3.get_text()

		SetConfiguracion.setModem3gConfiguracion(self.config3G)

	
	def levantarServicioNetworkManager(self):
		# Chequear si esta corriendo Network Manager
		ps = subprocess.Popen(['ps', '-A'], stdout=subprocess.PIPE).communicate()[0]
		if (not "NetworkManager" in ps.split()):
			# Iniciar Servicio
			ps = subprocess.Popen([PATH_WVDIAL, "nm-start"], stdout=subprocess.PIPE).communicate()[0]
			processes = ps.split('\n')
			self._logger.debug("Levantando servicio Network Manager....")

	def bajarServicioNetworkManager(self):
		ps = subprocess.Popen([PATH_WVDIAL, "nm-stop"], stdout=subprocess.PIPE).communicate()[0]
		processes = ps.split('\n')
		self._logger.debug("Bajando servicio Network Manager....")
	
	# Obtiene el estado de conexión segun los procesos corriendo en el sistema
	# Si hay un proceso corriendo obtiene el tiempo que lleva en ejecución
	def getStatusConexion(self):
		estado = "Desconectada"
		ps = subprocess.Popen(['ps', '-A','-f'], stdout=subprocess.PIPE).communicate()[0]
		processes = ps.split('\n')
		# print processes
		nfields = len(processes[0].split()) - 1
		for row in processes[1:]:
			if ("wvdial" in row.split() and not "<defunct>" in row.split()):
				# Hora de inicio del proceso
				time_start_hours = int(row.split()[4].partition(":")[0])
				time_start_minutes = int(row.split()[4].partition(":")[2])
				# Calculo tiempo de conexión
				if (time_start_hours > datetime.datetime.now().hour):
					time_start_days = datetime.datetime.now().day - 1
				else:
					time_start_days = datetime.datetime.now().day
				date_process = datetime.datetime(datetime.datetime.now().year, datetime.datetime.now().month, time_start_days, time_start_hours, time_start_minutes)
				date_diff = datetime.datetime.now() - date_process	
				self.timecount = date_diff.seconds
				estado = "Conectada"
		return estado

	# Devuelve los kb enviados/recibidos por la interfaz ppp0		
	def getDatosEnviadosConexion3G(self):
		try:
			lines = open('/proc/net/dev', 'r')
		except Exception, e:	
			self._logger.debug("Error leyendo el archivo /proc/net/dev"+str(e))
			raise e
		datosconexion = DatosConexion3G()
		encontreInterfaz = False
		for line in lines:
			if (string.find(line, "ppp0")<>-1):	
				line = line.partition(":")[2]
				datosconexion.BytesRecibidos = float(line.split()[0]) /1024
				datosconexion.BytesEnviados = float(line.split()[8]) /1024
				encontreInterfaz = True
		if (not encontreInterfaz):
			raise ExcepcionInterfazPPP0Desactivada("Interfaz ppp0 dada de baja")
		else:
			return datosconexion


# Clases Auxiliares
class DatosConexion3G:
	def __init__(self):
		self.BytesEnviados = 0
		self.BytesRecibidos = 0

class ExcepcionConfiguracionInvalida(Exception):
	pass

class ExcepcionInterfazPPP0Desactivada(Exception):
	pass

class ExcepcionErrorInstalacion(Exception):
	pass

if __name__ == '__main__':
	gtktest(False)










